// regex that contains all symbols applicable for expression used to AI detect an expression.
const expressionSymbolsRegex = /[\+\-\*\/%\?:<>=!\|&\(\)^~]|^`.*\$\{.+\}.*`$/;

export namespace bindingConstants {
	export const sourceProperty = 'sourceProperty';
	export const targetProperty = 'targetProperty';
	export const expression = 'expression';
	export const twoWay = 'twoWay';
	export const source = 'source';
	export const bindingValueKey = '$value';
	export const parentValueKey = '$parent';
	export const parentsValueKey = '$parents';
	export const newPropertyValueKey = '$newPropertyValue';
}

const hasEqualSignRegex = /=+/;
const equalSignComparisionOperatorsRegex = /(==|===|>=|<=|!=|!==)/;

// this regex is used to search for all instaces of '$parents[]' within an expression
export const parentsRegex = /\$parents\s*\[\s*(['"]*)\w*\1\s*\]/g;

function isNamedParam(value) {
	const equalSignIndex = value.search(hasEqualSignRegex);
	if (equalSignIndex > -1) {
		const equalSignSurround = value.substr(equalSignIndex > 0 ? equalSignIndex - 1 : 0, 3);
		if (equalSignSurround.search(equalSignComparisionOperatorsRegex) === -1) {
			return true;
		}
	}

	return false;
}

function areNamedParams(params: Array<any>) {
	for (let i = 0; i < params.length; i++) {
		if (isNamedParam(params[i])) {
			return true;
		}
	}

	return false;
}

const namedParamConstants = {
	propName: 'propName',
	propValue: 'propValue',
};

function getPropertyNameValuePair(param, knownOptions, callback) {
	let nameValuePair = {};
	let propertyName = param.substr(0, param.indexOf('=')).trim();
	const propertyValue = param.substr(param.indexOf('=') + 1).trim();
	if (knownOptions) {
		if (!propertyName) {
			propertyName = knownOptions.defaultProperty;
		} else {
			propertyName = propertyName in knownOptions ? propertyName : null;
		}
	}

	if (propertyName) {
		if (callback) {
			nameValuePair = callback(propertyName, propertyValue);
		} else {
			nameValuePair[namedParamConstants.propName] = propertyName;
			nameValuePair[namedParamConstants.propValue] = propertyValue;
		}

		return nameValuePair;
	}

	return null;
}

function parseNamedProperties(parameterList, knownOptions, callback) {
	const result = {};
	let nameValuePair;
	for (let i = 0; i < parameterList.length; i++) {
		nameValuePair = getPropertyNameValuePair(parameterList[i], knownOptions, callback);
		if (nameValuePair) {
			result[nameValuePair[namedParamConstants.propName]] = nameValuePair[namedParamConstants.propValue];
		}
	}

	return result;
}

function getParamsArray(value: string) {
	const result = [];
	let skipComma = 0;
	let indexReached = 0;
	let singleQuoteBlock = false;
	let doubleQuoteBlock = false;

	for (let i = 0; i < value.length; i++) {
		if (value[i] === '"') {
			doubleQuoteBlock = !doubleQuoteBlock;
		}

		if (value[i] === "'") {
			singleQuoteBlock = !singleQuoteBlock;
		}

		if (value[i] === '(' || value[i] === '[') {
			skipComma++;
		}

		if (value[i] === ')' || value[i] === ']') {
			skipComma--;
		}

		if (value[i] === ',' && skipComma === 0 && !(singleQuoteBlock || doubleQuoteBlock)) {
			result.push(value.substr(indexReached, i - indexReached));
			indexReached = i + 1;
		}
	}

	result.push(value.substr(indexReached));

	return result;
}

function isExpression(expression: string): boolean {
	return expression.search(expressionSymbolsRegex) > -1;
}

export function getBindingOptions(name: string, value: string): any {
	let namedParams = [];
	const params = getParamsArray(value);
	if (!areNamedParams(params)) {
		if (params.length === 1) {
			const trimmedValue = params[0].trim();
			let sourceProp;
			if (isExpression(trimmedValue)) {
				sourceProp = bindingConstants.bindingValueKey;
				namedParams.push(bindingConstants.expression + ' = ' + trimmedValue);
			} else {
				sourceProp = trimmedValue;
			}
			namedParams.push(bindingConstants.sourceProperty + ' = ' + sourceProp);

			namedParams.push(bindingConstants.twoWay + ' = true');
		} else {
			namedParams.push(bindingConstants.sourceProperty + ' = ' + params[0].trim());
			namedParams.push(bindingConstants.expression + ' = ' + params[1].trim());
			const twoWay = params[2] ? params[2].toLowerCase().trim() === 'true' : true;
			namedParams.push(bindingConstants.twoWay + ' = ' + twoWay);
		}
	} else {
		namedParams = params;
	}

	const bindingPropertyHandler = function (prop, value) {
		const result = {};
		result[namedParamConstants.propName] = prop;
		if (prop === bindingConstants.twoWay) {
			// create a real boolean value
			if (value === 'true') {
				result[namedParamConstants.propValue] = true;
			} else {
				result[namedParamConstants.propValue] = false;
			}
		} else {
			result[namedParamConstants.propValue] = value;
		}

		return result;
	};

	const bindingOptionsParameters = parseNamedProperties(namedParams, xmlBindingProperties, bindingPropertyHandler);
	const bindOptions = {
		targetProperty: name,
	};

	for (const prop in bindingOptionsParameters) {
		if (bindingOptionsParameters.hasOwnProperty(prop)) {
			bindOptions[prop] = bindingOptionsParameters[prop];
		}
	}

	if (bindOptions[bindingConstants.twoWay] === undefined) {
		bindOptions[bindingConstants.twoWay] = true;
	}

	return bindOptions;
}

const xmlBindingProperties = {
	sourceProperty: true,
	expression: true,
	twoWay: true,
	source: true,
	defaultProperty: bindingConstants.sourceProperty,
};
